{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Decorators (Part 1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Recall the example in the last section where we wrote a simple closure to count how many times a function had been run:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def counter(fn):\n",
    "    count = 0\n",
    "    \n",
    "    def inner(*args, **kwargs):\n",
    "        nonlocal count\n",
    "        count += 1\n",
    "        print('Function {0} was called {1} times'.format(fn.__name__, count))\n",
    "        return fn(*args, **kwargs)\n",
    "    return inner"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def add(a, b=0):\n",
    "    \"\"\"\n",
    "    returns the sum of a and b\n",
    "    \"\"\"\n",
    "    return a + b"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on function add in module __main__:\n",
      "\n",
      "add(a, b=0)\n",
      "    returns the sum of a and b\n",
      "\n"
     ]
    }
   ],
   "source": [
    "help(add)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's the memory address that `add` points to:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2352389334696"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "id(add)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we create a closure using the `add` function as an argument to the `counter` function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "add = counter(add)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And you'll note that `add` is no longer the same function as before. Indeed the memory address `add` points to is no longer the same:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2352404346128"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "id(add)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Function add was called 1 times\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "3"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "add(1, 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Function add was called 2 times\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "4"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "add(2, 2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What happened is that we put our **add** function 'through' the **counter** function - we usually say that we **decorated** our function **add**.\n",
    "\n",
    "And we call that **counter** function a **decorator**.\n",
    "\n",
    "There is a shorthand way of decorating our function without having to type:\n",
    "\n",
    "``func = counter(func)``"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "@counter\n",
    "def mult(a: float, b: float=1, c: float=1) -> float:\n",
    "    \"\"\"\n",
    "    returns the product of a, b, and c\n",
    "    \"\"\"\n",
    "    return a * b * c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Function mult was called 1 times\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "6"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mult(1, 2, 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Function mult was called 2 times\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "8"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mult(2, 2, 2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's do a little bit of introspection on our two decorated functions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'inner'"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "add.__name__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'inner'"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mult.__name__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, the name of the function is no longer **add** or **mult**, but instead it is the name of that **inner** function in our decorator."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on function inner in module __main__:\n",
      "\n",
      "inner(*args, **kwargs)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "help(add)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on function inner in module __main__:\n",
      "\n",
      "inner(*args, **kwargs)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "help(mult)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, we've also lost our docstring and parameter annotations!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What about introspecting the parameters of **add** and **mult**:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import inspect"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "\"    def inner(*args, **kwargs):\\n        nonlocal count\\n        count += 1\\n        print('Function {0} was called {1} times'.format(fn.__name__, count))\\n        return fn(*args, **kwargs)\\n\""
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "inspect.getsource(add)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "\"    def inner(*args, **kwargs):\\n        nonlocal count\\n        count += 1\\n        print('Function {0} was called {1} times'.format(fn.__name__, count))\\n        return fn(*args, **kwargs)\\n\""
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "inspect.getsource(mult)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Even the signature is gone:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Signature (*args, **kwargs)>"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "inspect.signature(add)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Signature (*args, **kwargs)>"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "inspect.signature(mult)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Even the parameter defaults documentation is are gone:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "mappingproxy({'args': <Parameter \"*args\">, 'kwargs': <Parameter \"**kwargs\">})"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "inspect.signature(add).parameters"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In general, when we create decorated functions, we end up \"losing\" a lot of the metadata of our original function!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "However, we **can** put that information back in - it can get quite complicated.\n",
    "\n",
    "Let's see how we might be able to do that for some simple things, like the docstring and the function name."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def counter(fn):\n",
    "    count = 0\n",
    "    \n",
    "    def inner(*args, **kwargs):\n",
    "        nonlocal count\n",
    "        count += 1\n",
    "        print(\"{0} was called {1} times\".format(fn.__name__, count))\n",
    "    inner.__name__ = fn.__name__\n",
    "    inner.__doc__ = fn.__doc__\n",
    "    return inner"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "@counter\n",
    "def add(a: int, b: int=10) -> int:\n",
    "    \"\"\"\n",
    "    returns sum of two integers\n",
    "    \"\"\"\n",
    "    return a + b"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on function add in module __main__:\n",
      "\n",
      "add(*args, **kwargs)\n",
      "    returns sum of two integers\n",
      "\n"
     ]
    }
   ],
   "source": [
    "help(add)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'add'"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "add.__name__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "At least we have the docstring and function name back... But what about the parameters? Our real **add** function takes two positional parameters, but because the closure used a generic way of accepting **\\*args** and **\\*\\*kwargs**, we lose this information"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can use a special function in the **functools** module, called **wraps**. In fact, that function is a decorator itself!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from functools import wraps"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def counter(fn):\n",
    "    count = 0\n",
    "    \n",
    "    @wraps(fn)\n",
    "    def inner(*args, **kwargs):\n",
    "        nonlocal count\n",
    "        count += 1\n",
    "        print(\"{0} was called {1} times\".format(fn.__name__, count))\n",
    "        return fn(*args, **kwargs)\n",
    "\n",
    "    return inner"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "@counter\n",
    "def add(a: int, b: int=10) -> int:\n",
    "    \"\"\"\n",
    "    returns sum of two integers\n",
    "    \"\"\"\n",
    "    return a + b"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on function add in module __main__:\n",
      "\n",
      "add(a:int, b:int=10) -> int\n",
      "    returns sum of two integers\n",
      "\n"
     ]
    }
   ],
   "source": [
    "help(add)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Yay!!! Everything is back to normal."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'@counter\\ndef add(a: int, b: int=10) -> int:\\n    \"\"\"\\n    returns sum of two integers\\n    \"\"\"\\n    return a + b\\n'"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "inspect.getsource(add)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Signature (a:int, b:int=10) -> int>"
      ]
     },
     "execution_count": 31,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "inspect.signature(add)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "mappingproxy({'a': <Parameter \"a:int\">, 'b': <Parameter \"b:int=10\">})"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "inspect.signature(add).parameters"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
